/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.jaywalkable.vkeyboard;

import java.security.InvalidParameterException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Utility class for <code>VKeyboard</code> implementations. This class instance
 * is used to create <code>VKeyboardEncoder</code> and <code>VKeyboardDecoder
 * </code> of <code>VKeyboard</code> implementations.
 *
 * Use <code>forName</code> method to create instance of this class. Parameter
 * name can be any name of build-in <code>VKeyboard</code> implementations. 
 * You can use fully qualified <code>class</code> name of your own implemntation
 * of <code>VKeyboard</code>. Use "BN_UNIJOY" to obtain instance of one such
 * implementation of this class for Bengali Unijoy keyboard available in this library.
 *
 * @author Rubel Ahammad
 */
public final class VKeyboardEncoding {

    private static final Map<String, VKeyboardEncoding> encodings = new HashMap<String, VKeyboardEncoding>();
    private final VKeyboard keyboard;

    private VKeyboardEncoding(VKeyboard keyboard) {
        this.keyboard = keyboard;
    }

    /**
     * Returns the instance of <code>VKeyboardEncoding</code> associated with
     * <code>VKeyboard</code> implementation.
     * 
     * @param name the name of encoding scheme.
     * @return VKeyboardEncoding the instance of <code>VKeyboardEncoding</code>
     * associated with <code>VKeyboard</code> implementation.
     */
    public static VKeyboardEncoding forName(String name) {
        VKeyboard keyboard = null;
        VKeyboardEncoding enc = encodings.get(name);
        if (enc != null) {
            return enc;
        }
        if ("BN_UNIJOY".equalsIgnoreCase(name)) {
            keyboard = new UnijoyVKeyboard();
        } else {
            try {
                keyboard = (VKeyboard) Class.forName(name).newInstance();
            } catch (Exception ex) {
                Logger.getLogger(VKeyboardEncoding.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        if (keyboard == null) {
            throw new InvalidParameterException("Invalid name provided.");
        } else {
            enc = new VKeyboardEncoding(keyboard);
            encodings.put(name, enc);
        }
        return enc;
    }

    /**
     * Returns the <code>VKeyboardEncoder</code> associated with <code>VKeyboard</code>
     * implementation.
     * @return VKeyboardEncoder the encoder associated with <code>VKeyboard</code>
     * implementation.
     */
    public VKeyboardEncoder newEncoder() {
        return this.new Encoder();
    }

    /**
     * Returns the <code>VKeyboardDecoder</code> associated with <code>VKeyboard</code>
     * implementation.
     * @return VKeyboardDecoder the decoder associated with <code>VKeyboard</code>
     * implementation.
     */
    public VKeyboardDecoder newDecoder() {
        return this.new Decoder();
    }

    private class Encoder implements VKeyboardEncoder {

        /** TreeMap used to sort elements based on String length of its keys. */
        private final TreeMap<String, Character> map = new TreeMap<String, Character>(new Comparator<String>() {

            /**
             * Compares the length first. If both are of same length then default
             * <code>String</code> comparison is returned.
             */
            public int compare(String o1, String o2) {
                int lenComp = Integer.valueOf(o1.length()).compareTo(Integer.valueOf(o2.length()));
                return lenComp != 0 ? lenComp : o1.compareTo(o2);
            }
        });

        /**
         * Populates the <code>TreeMap</code> with keyboard layout values.
         */
        Encoder() {
            Map<Character, Character[]> layout = keyboard.getLayout();
            for (Character c : layout.keySet()) {
                Character[] chars = layout.get(c);
                String str = createString(chars);
                map.put(str, c);
            }
        }

        public String encode(String str) {
            String encoded = str;
            for (String key : map.descendingKeySet()) {
                encoded = encoded.replace(key, String.valueOf(map.get(key)));
            }
            return encoded;
        }

        public Character encode(Character[] chars) {
            String key = createString(chars);
            return map.get(key);
        }

        private String createString(Character[] chars) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < chars.length; i++) {
                sb.append(chars[i]);
            }
            return sb.toString();
        }
    }

    private class Decoder implements VKeyboardDecoder {

        private final Map<Character, Character[]> map;

        Decoder() {
            map = keyboard.getLayout();
        }

        public Character[] decode(Character cin) {
            Character[] cout = map.get(cin);
            if (cout == null) {
                return new Character[]{cin};
            }
            return cout;
        }
    }
}
